/*
 * Copyright (C) 2012 Atlas of Living Australia
 * All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 */

package au.org.ala.phylojive

import au.com.bytecode.opencsv.CSVWriter
import grails.converters.JSON

import java.util.regex.Pattern

/**
 * Various utility methods for dealing with the characters data
 *
 * charJSON format is expected for phylojive. E.g.
 *
 *  { "Macropus rufus": {
 *      "fur colour": [ "brown" ],
 *      "height range (in m)": [ 1, 3 ],
 *      "life expectancy": [" 12 years"],
 *      "distribution (states)": ["Western Australia","New South Wales","South Australia"]
 *      },
 *  "Macropus giganteus": {
 *      "fur colour": [ "grey" ],
 *      "height range (in m)": [ 1, 2 ],
 *      "life expectancy": [" 14 years"],
 *      "distribution (states)": ["Victoria","New South Wales","South Australia"]
 *      }
 *  }
 */
class CharService {

    /**
     * Convert CSV format to charJSON
     *
     * CVS format example:
     *
     * "scientificName","seed mass","Inflorescence colour","plant height","Phyllode length median","Inflorescence arrangement","Stipule length median","Inflorescence shape"," leaflet pairs 2nd leaf","range size","genome size","Phyllode arrangement","Pulvinus length median","first leaf pinnae pairs","section taxonomy"
     * "Acacia diphylla","5.4","unknown","","","unknown","","unknown","1.9","","","unknown","","2.0","unknown"
     * "Acacia courtii","","white to cream||pale yellow","20","115","simple","0.25","cylindrical","","2","","scattered","","","juliflorae"
     *
     * Note, character values get converted to a JSON array and can thus have multiple values. This is achieved by using
     * an internal separator string (arg 2) with default value of "||".
     *
     * Can be tested with CharServiceTests.groovy. Output was checked to be valid JSON via http://jsonlint.com/
     *
     * @param charCsv
     * @param internalSeparator
     * @return
     */
    def convertCharCsvToJson(String charCsv, String internalSeparator) {
        def lineCount = 0;
        def headers = [] // Names of characters
        def charMap = [:] // Map version of JSON format
        internalSeparator = Pattern.quote(internalSeparator?:"||") // separator string needs escaping as per a regex
        log.debug "input csv = " + charCsv
        charCsv.eachCsvLine { tokens ->
            log.debug "tokens = " + tokens
            if (lineCount == 0) {
                // assume first line is header with character names
                headers = tokens[1..<tokens.size()] // ignore first field as it is taxon name
            } else {
                // data lines
                def thisChars = [:]
                def name = tokens[0] // taxon name
                def charValues = tokens[1..<tokens.size()] // characters

                charValues.eachWithIndex() { obj, i ->
                    def rawValue = obj?:null // force empty string to be null
                    def values = rawValue?.split(internalSeparator);
                    def typedValues = []
                    values.each() {
                        // coerce values into their proper types (Float, Boolean or String)
                        if (it.isFloat()) {
                            typedValues.add(it.toFloat())
                        } else if (it ==~ /(?i)^(true|false)$/) {
                            typedValues.add(it.toBoolean())
                        } else {
                            typedValues.add(it)
                        }
                    }
                    thisChars.put(headers[i], typedValues)
                }

                charMap.put(name, thisChars)
            }
            log.debug(lineCount + ". " + charMap);

            lineCount++;
        }

        //def jsonOutput = new groovy.json.JsonBuilder( charMap ).toString()
        //log.debug("JSON output: " + jsonOutput);

        return charMap
    }

    /**
     * Convert CharJson format to csv
     *
     * @param charJson
     * @param internalSeparator
     */
    def convertCharJsonToCsv(String charJsonStr, String internalSeparator) {
        def chars = JSON.parse(charJsonStr) //  returns JsonElement: either JsonArray or JsonObject
        def csvStr
        def separator = internalSeparator?:"||"
        def firstField = "Scientific Name"
        //def headers = ["scientificName"] // taxon goes on front of CSV line
        def headersMap = [:]
        headersMap.put(firstField, 0)
        //def csvWriter = new CSVWriter(writer)
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        CSVWriter csvWriter = new CSVWriter(new OutputStreamWriter(baos))

        // first pass to build complete list of character names
        chars.each { ch ->
            def props = ch.value

            if (props) {
                props.each { p ->
                    def charName = p.key

                    if (headersMap.containsKey(charName)) {
                        def count = headersMap.get(charName)
                        headersMap.put(charName, count + 1)
                    } else {
                        headersMap.put(charName, 1)
                    }
                }
            }
        }

        log.debug "headersMap = " + headersMap

        def headers = []
        headersMap.each { h, c ->
            //log.debug "header = " + h
            headers.add(h)
        }

        csvWriter.writeNext(headers as String[])
        //headerAdded = true

        // second pass to populate the table
        chars.each { ch ->
            def name = ch.key
            def props = ch.value

            if (!props) {
                // taxa has no characters -> generate an empty list of chars so CSV has equal number of entries
                //log.debug "empty props = " + headers.size()
                def values = [name]
                (1..headers.size()-1).each {
                    values.add("") // add empty fields
                }
                //log.debug("E: values size = " + values.size())
                csvWriter.writeNext(values as String[])
            } else {
                // write characters
                def values = [name]

                headers.each { charName ->
                    def newProps = []

                    if (props.containsKey(charName)) {
                        //log.debug(name + " has character: " + charName + " with value = " + props.get(charName))
                        def propValues = props.get(charName)
                        propValues.each {
                            newProps.add(it)
                        }
                        values.add(newProps.join(separator))
                    } else if (charName != firstField) {
                        //log.debug(name + " is missing character: " + charName)
                        values.add("")
                    }
                }

                //log.debug("F: values size = " + values.size())
                //println "values = " + values.join("|")
                csvWriter.writeNext(values as String[])
            }
        }

        csvWriter.flush()
        csvStr = baos.toString( "UTF-8" )
        csvWriter.close()

        return csvStr
    }
}
